/*
Copyright (C) 2022-2024 Inspur Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package service

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/bitly/go-simplejson"
	"github.com/cbergoon/merkletree"
	"github.com/emicklei/go-restful"
	"github.com/go-xorm/xorm"
	"github.com/pkg/errors"

	"did-service/bean/entity"
	"did-service/bean/model"
	"did-service/bean/param"
	"did-service/chain/client"
	chainModel "did-service/chain/model"
	"did-service/common/constant"
	"did-service/common/crypto/gm"
	"did-service/common/db"
	"did-service/common/uuid"
)

const domain = "localhost"

// 四表联查
type presentationCredentialCptIssuer struct {
	entity.VerifiablePresentation `xorm:"extends"`
	entity.VerifiableCredential   `xorm:"extends"`
	entity.CredentialCpt          `xorm:"extends"`
	entity.AuthorityIssuer        `xorm:"extends"`
}

// ValidateCredential 校验凭证是否存在、凭证状态、凭证是否归属当前用户
func (ds *DidService) ValidateCredential(currentWalletId, credentialId string) (bool, string, *model.CredentialDetail, error) {
	credValid, _, credDetail, err := getCredBytes(ds.engine, credentialId)
	if err != nil {
		//凭证不存在
		if strings.Contains(err.Error(), "does not exist") {
			return false, "凭证不存在", nil, nil
		}
		return false, "", nil, err
	}
	//凭证当前不可用
	if !credValid {
		return false, "凭证不可用", nil, nil
	}
	//校验凭证是否归属当前用户
	err = verifyCredOwner(ds.engine, currentWalletId, credentialId)
	if err != nil {
		if strings.Contains(err.Error(), "not the owner") {
			return false, "凭证不属于当前用户", nil, nil
		}
		return false, "", nil, err
	}
	return true, "", credDetail, nil
}

func (ds *DidService) ValidateClaims(userClaims, credId string) (ok bool, errInfo string, err error) {
	credClaims := make([]*entity.VerifiableCredentialClaim, 0)
	err = ds.engine.Where("credential_id = ?", credId).Find(&credClaims)
	if err != nil {
		return false, "", err
	}

	useClaimsJson, err := simplejson.NewJson([]byte(userClaims))
	if err != nil {
		return false, "", err
	}

	//遍历比对Claims属性
	for _, credClaim := range credClaims {
		var val string
		switch credClaim.ValueType {
		case constant.CptClaimValueTypeString, constant.CptClaimValueTypeDate:
			val, err = useClaimsJson.Get(credClaim.Property).String()
		case constant.CptClaimValueTypeNum:
			var intVal int64
			intVal, err = useClaimsJson.Get(credClaim.Property).Int64()
			val = strconv.FormatInt(intVal, 10)
		case constant.CptClaimValueTypeBool:
			var boolVal bool
			boolVal, err = useClaimsJson.Get(credClaim.Property).Bool()
			val = strconv.FormatBool(boolVal)
		}
		if err != nil {
			return false, "", err
		}
		if val != "" && val != credClaim.Value {
			return false, "验证Claims不通过", nil
		}
	}
	return true, "", nil
}

type treeContent struct {
	content string
}

func (t treeContent) CalculateHash() ([]byte, error) {
	h := sha256.New()
	if _, err := h.Write([]byte(t.content)); err != nil {
		return nil, err
	}
	return h.Sum(nil), nil
}

func (t treeContent) Equals(other merkletree.Content) (bool, error) {
	return t.content == other.(treeContent).content, nil
}

func CalcMerkleRoot(paras ...string) ([]byte, error) {
	var list []merkletree.Content
	for _, para := range paras {
		list = append(list, treeContent{content: para})
	}
	//Create a new Merkle Tree from the list of Content
	tree, err := merkletree.NewTree(list)
	if err != nil {
		return nil, err
	}
	return tree.MerkleRoot(), nil
}

// GeneratePresentation 生成 Verifiable Presentation
func (ds *DidService) GeneratePresentation(walletId, vpId string, cred *model.Credential, para *param.ApplyPresentationParam) error {
	//对凭证主体did、凭证声明属性值、发证方did、凭证颁发时间、凭证过期时间计算默克尔根哈希
	h1, err := CalcMerkleRoot(cred.Id, cred.CredentialSubject.Claims, cred.Issuer, cred.IssuanceDate, cred.ExpirationDate)
	if err != nil {
		return err
	}
	//对h1、附加信息、挑战随机数、和挑战域再次计算默克尔根哈希
	h2, err := CalcMerkleRoot(hex.EncodeToString(h1), para.AdditionalInfo, para.Challenge, constant.DefaultVpDomain)
	if err != nil {
		return err
	}
	//获取凭证did主公钥对应的私钥
	prvKey, keyId, _, err := getPrvKey(ds.engine, cred.CredentialSubject.Id, 1)
	if err != nil {
		return err
	}
	//使用私钥对h2进行签名
	h2Sign := gm.Sign(prvKey, h2)

	err = insertPresentation(ds.engine, walletId, vpId, keyId, h2Sign, para)
	if err != nil {
		return err
	}
	err = insertAdditionalInfo(ds.engine, vpId, para)
	if err != nil {
		return err
	}
	txId, err := vpOnChain(ds.getChainClient(), cred, vpId, para.Challenge, h2Sign)
	if err != nil {
		return err
	}
	//更新vp表中的txId
	_, err = ds.engine.Where("id = ?", vpId).Cols("tx_id").
		Update(&entity.VerifiablePresentation{TxId: txId})
	if err != nil {
		return err
	}
	return nil
}

// 插入vp表
func insertPresentation(engine *xorm.Engine, walletId, vpId, keyId, sign string, para *param.ApplyPresentationParam) error {
	verPres := entity.VerifiablePresentation{
		Id:             vpId,
		Name:           para.Name,
		CredentialId:   para.CredId,
		OwnerId:        walletId,
		ChainStoreType: constant.CredentialOnChainTypeMerkle,
		Challenge:      para.Challenge,
		Domain:         constant.DefaultVpDomain,
		ProofCreator:   keyId,
		ProofType:      constant.SignTypeSm2p256v1,
		ProofSignature: sign,
		Status:         constant.PresentationStatusValid,
		CreatedTime:    time.Now(),
		UpdatedTime:    time.Now(),
	}
	//插入可验证凭证属性表
	if _, err := engine.Insert(&verPres); err != nil {
		return err
	}
	return nil
}

func insertAdditionalInfo(engine *xorm.Engine, vpId string, para *param.ApplyPresentationParam) error {
	addInfoJson, err := simplejson.NewJson([]byte(para.AdditionalInfo))
	infoJsonMap, err := addInfoJson.Map()
	if err != nil {
		return err
	}
	addInfos := make([]*entity.VpAdditionalInfo, 0)
	seq := 1
	for key, value := range infoJsonMap {
		addInfo := entity.VpAdditionalInfo{
			Id:          uuid.Generate(),
			VpId:        vpId,
			InfoName:    key,
			InfoValue:   fmt.Sprintf("%s", value),
			Seq:         seq,
			CreatedTime: time.Now(),
			UpdatedTime: time.Now(),
		}
		seq++
		addInfos = append(addInfos, &addInfo)
	}
	if _, err := engine.Insert(&addInfos); err != nil {
		return err
	}
	return nil
}

// 将vp上链
func vpOnChain(chainClient *client.IchainClient, cred *model.Credential, vpId, challenge, proofSign string) (string, error) {
	if chainClient == nil {
		return "", errors.New("failed to get chain Client")
	}
	vpChain := chainModel.PresentationInfo{
		Id:           vpId,
		CredentialId: cred.Id,
		Challenge:    challenge,
		Domain:       constant.DefaultVpDomain,
		Signature:    proofSign,
	}
	txId, _ := chainClient.SavePresentationInfo(&vpChain)
	return txId, nil
}

// ParseQueryParam 解析Query参数,根据json标签匹配参数
func ParseQueryParam(req *restful.Request, s interface{}) {
	rt := reflect.TypeOf(s)
	rv := reflect.ValueOf(s)
	if rv.Kind() == reflect.Ptr {
		rt = rt.Elem()
		rv = rv.Elem()
	}

	numField := rt.NumField()
	for i := 0; i < numField; i++ {
		tField := rt.Field(i)
		tagValue, ok := tField.Tag.Lookup("json")
		if ok {
			vField := rv.FieldByName(strings.Title(tagValue))
			para := req.QueryParameter(tagValue)
			vField.SetString(para)
		}
	}
}

// QueryPresentationList VP列表
func (ds *DidService) QueryPresentationList(walletId string, para *param.ListPresentationParam) (*db.Page, error) {
	condition := ""
	args := make([]interface{}, 0)
	presentationList := make([]*model.PresentationInfo, 0)

	pageSize := 10
	if para.PageSize != "" {
		if size, err := strconv.Atoi(para.PageSize); err == nil {
			pageSize = size
		}
	}
	pageNum := 1
	if para.PageNum != "" {
		if num, err := strconv.Atoi(para.PageNum); err == nil {
			pageNum = num
		}
	}
	page := db.NewPage(0, pageNum).SetPageSize(pageSize)

	MultiDid, err := walletId2MultiDid(ds.engine, walletId)
	if err != nil {
		return nil, err
	}
	//获取有效的发证方did
	validIssuerDid, err := getValidIssuerDid(ds.engine, MultiDid, true)
	if err != nil {
		return nil, err
	}
	//用户持有凭证的发证方所有did均被停用
	if len(validIssuerDid) == 0 {
		page.SetTotal(0)
		page.CreatePage(presentationList)
		return page, nil
	}

	//不包括停用的VP
	combineSql("vp.status = ? ", &condition, constant.PresentationStatusValid, &args)
	//不包括停用的凭证对应的VP
	combineSql(" and vc.status = ? ", &condition, constant.CredentialStatusValid, &args)

	//发证方名称
	if para.IssuerName != "" {
		s := " and ai.name like " + "'%" + para.IssuerName + "%' "
		combineSql(s, &condition, "", nil)
	}
	//凭证名称
	if para.CredentialName != "" {
		s := " and cc.title like " + "'%" + para.CredentialName + "%' "
		combineSql(s, &condition, "", nil)
	}
	//VP名称
	if para.PresentationName != "" {
		s := " and vp.name like " + "'%" + para.PresentationName + "%' "
		combineSql(s, &condition, "", nil)
	}

	//所有者Did
	combineSql(" and vp.owner_id = ? ", &condition, walletId, &args)

	//发证方筛选条件
	combineSql(" and (vc.issuer_did = ? ", &condition, validIssuerDid[0], &args)
	if len(validIssuerDid) > 1 {
		for _, did := range validIssuerDid[1:] {
			combineSql(" or vc.issuer_did = ? ", &condition, did, &args)
		}
	}
	combineSql(")", &condition, "", nil)

	session := getVPJoinSession(ds.engine)
	var countEntity presentationCredentialCptIssuer
	listNum, err := session.Where(condition, args...).Count(&countEntity)
	if err != nil {
		return nil, errors.Errorf("query verifiable presentation error: %s", err)
	}
	if listNum == 0 {
		page.SetTotal(0)
		page.CreatePage(presentationList)
		return page, nil
	}

	session = getVPJoinSession(ds.engine)
	list := make([]*presentationCredentialCptIssuer, 0)
	err = session.Where(condition, args...).Limit(pageSize, (pageNum-1)*pageSize).Desc("vp.updated_time").Find(&list)
	if err != nil {
		return nil, errors.Errorf("query verifiable presentation list error: %s", err)
	}

	for _, res := range list {
		presentationList = append(presentationList, &model.PresentationInfo{
			PresentationId:   res.VerifiablePresentation.Id,
			PresentationName: res.VerifiablePresentation.Name,
			CredentialName:   res.CredentialCpt.Title,
			IssuerName:       res.AuthorityIssuer.Name,
			IssuanceDate:     res.VerifiablePresentation.CreatedTime.Format(constant.TimeLayout),
		})
	}
	page.SetTotal(listNum)
	page.CreatePage(presentationList)
	return page, nil
}

func getVPJoinSession(engine *xorm.Engine) *xorm.Session {
	return engine.Table("verifiable_presentation").Alias("vp").
		Join("LEFT", []string{"verifiable_credential", "vc"}, "vp.credential_id = vc.id").
		Join("LEFT", []string{"credential_cpt", "cc"}, "cc.id = vc.cpt_id").
		Join("LEFT", []string{"authority_issuer", "ai"}, "ai.id = vc.issuer_id")
}

func (ds *DidService) QueryPresentationDetail(walletId, presentationId string) (*model.PresentationDetail, error) {
	err := verifyVpOwner(ds.engine, walletId, presentationId)
	if err != nil {
		return nil, err
	}

	presDetail, err := getVpDetail(ds.engine, presentationId)
	if err != nil {
		return nil, err
	}
	return presDetail, nil
}

// 验证walletId和请求的presentation id是否匹配
func verifyVpOwner(engine *xorm.Engine, walletId, presentationId string) error {
	var vp entity.VerifiablePresentation
	count, err := engine.Where("id = ? and owner_id = ?", presentationId, walletId).Count(&vp)
	if err != nil {
		return err
	}
	if count == 0 {
		return errors.Errorf("walletId:%s is not the owner of presentationId:%s", walletId, presentationId)
	}
	return nil
}

func getVpDetail(engine *xorm.Engine, presentationId string) (*model.PresentationDetail, error) {
	//查询VP表
	vp := new(entity.VerifiablePresentation)
	ok, err := engine.Where("id = ?", presentationId).Get(vp)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errors.Errorf("the presentation does not exist, presentationId :%s", presentationId)
	}
	//获取凭证内容
	_, _, credDetail, err := getCredBytes(engine, vp.CredentialId)
	if err != nil {
		return nil, err
	}

	//查询vp_additional_info表
	addInfo := make([]*entity.VpAdditionalInfo, 0)
	err = engine.Where("vp_id = ?", presentationId).Find(&addInfo)
	if err != nil {
		return nil, err
	}

	pres := &model.Presentation{
		Context:              []string{constant.CredentialContext},
		Id:                   vp.Id,
		Type:                 constant.VerifiablePresentation,
		VerifiableCredential: credDetail.Credential,
		AdditionalInfo:       parseAdditionalInfo(addInfo),
		Proof: model.VpProof{
			Type:               constant.DefaultKeyAlgo,
			Created:            vp.CreatedTime.Format(constant.TimeLayout),
			ProofPurpose:       constant.Authentication,
			VerificationMethod: transKeyType(vp.ProofCreator),
			Challenge:          vp.Challenge,
			Domain:             vp.Domain,
			ProofValue:         vp.ProofSignature,
		},
	}

	presDetail := &model.PresentationDetail{
		Presentation: pres,
		TxId:         vp.TxId,
	}

	if err != nil {
		return nil, err
	}
	return presDetail, nil
}

var parseAdditionalInfo = func(info []*entity.VpAdditionalInfo) map[string]string {
	infoMap := make(map[string]string, 0)
	for _, i := range info {
		infoMap[i.InfoName] = i.InfoValue
	}
	return infoMap
}

// key格式转换
var transKeyType = func(key string) string {
	res := strings.Split(key, "/")
	if len(res) != 2 {
		return key
	}
	return fmt.Sprintf("%s#keys-%s", res[0], res[1])
}
